# This file is part of the AutoMAT distribution (https://bitbucket.com/mahomaho/AutoMAT).
# Copyright (c) 2020 Mattias Holmqvist.
# 
# AutoMAT is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# AutoMAT is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with AutoMAT.  If not, see <https://www.gnu.org/licenses/>.

from copy import deepcopy
import functools
import math
import bisect

from lxml import etree as _ET

from . import *
from . import autosar_r4p0
from . import files as fileH
from .autosar_r4p0 import Group
from .complexbase import *
from .support import *
from . import simplebase


def _createxml(cont,element,file,newtype,setup):
	fileH.modified(file)
	parentlist=((type(cont),[]),)
	xmlparent=None
	while True:
		nextlist=[]
		for parent,path in parentlist:
			for xmlname,(e,_) in parent._xmlchildren.items():
				if e is element:
					xmlparent=xmlname
					break
					#return path,xmlname
				elif type(e._desttype) is dict:
					if len(e._desttype)==1:
						desttype=e._desttype.values().__iter__().__next__()
						nextlist.append((desttype,(*path,(e._name,desttype))))
				elif issubclass(e._desttype,ComplexTypeBase):
					nextlist.append((e._desttype,(*path,(e._name,e._desttype))))
		if xmlparent is not None:
			break
		assert len(nextlist) > 0, 'bug in automat'
		parentlist=nextlist
	xmlparent=cont
	_complement(xmlparent,file)
	for p,desttype in path:
		ret=getattr(xmlparent,p)
		if ret==ModelNone:
			xmlparent=desttype._create(xmlparent,getattr(type(xmlparent),p),file,setup=setup)
		else:
			if isinstance(ret,ModelList):
				xmlparent=ret[0]
			else:
				xmlparent=ret				
			_complement(xmlparent,file)

	parentxml=next((xml for xml in xmlparent._xml if xml.base==file))
	if isinstance(element,Elements):
		# add eventual group
		if isinstance(element._desttype,dict):
			try:
				groupparentxml=next(parentxml.iterchildren(AR_NS+xmlname))
			except:
				_element, key = xmlparent._xmlchildren[xmlname]
				groupparentxml = parentxml.makeelement(AR_NS+xmlname)
				for child in parentxml:
					if xmlparent._xmlchildren[no_ns(child.tag)][1] > key:
						child.addprevious(groupparentxml)
						break
				else:
					parentxml.append(groupparentxml)
			assert newtype is not None,'Cannot create child to node where multiple types are supported without type specified'
			# skip sort
			newxml=ET.SubElement(groupparentxml,AR_NS+next((xname for xname,atype in element._desttype.items() if atype is newtype)))
			newxml.text=''
		else:
			newtype=element._desttype
			_element, key = xmlparent._xmlchildren[xmlname]
			newxml = parentxml.makeelement(AR_NS+xmlname)
			for child in parentxml:
				if xmlparent._xmlchildren[no_ns(child.tag)][1] > key:
					child.addprevious(newxml)
					break
			else:
				parentxml.append(newxml)
			newxml.text=''
	else:
		newtype=element._desttype
		_element, key = xmlparent._xmlchildren[xmlname]
		newxml = parentxml.makeelement(AR_NS+xmlname)
		for child in parentxml:
			if xmlparent._xmlchildren[no_ns(child.tag)][1] > key:
				child.addprevious(newxml)
				break
		else:
			parentxml.append(newxml)
		newxml.text=''
	return newxml

def _complement(cont,file):
	parentxml = next((xml for xml in cont._xml if str(file) == xml.base), None)
	if parentxml is not None:
		return parentxml
	if cont._parent is ModelNone:
		assert not hasattr(file,'_root'), 'Automat error: file handler has a reference to xml but is not loaded into autosar'
		xsi = "http://www.w3.org/2001/XMLSchema-instance"
		tree = _ET.Element(
			"{"+autosar_r4p0.AR_NS+"}AUTOSAR",
			attrib={"{" + xsi + "}schemaLocation": autosar_r4p0.schemaLocation},
			nsmap={'xsi': xsi, None: autosar_r4p0.AR_NS}
		)
		tree.base = str(file)
		file._root=_ET.ElementTree(element=tree, file=str(file))
		cont._xml=(*cont._xml,tree)
		fileH.modified(file)
		return tree
		
	assert cont._binding._splitable, f'current action will result in creation of a container split at {cont.__repr__()} for a non splitable container.\nContainer exists in file {cont.file()} and request is to create container in file {file}'
	parentxml = _complement(cont._parent, file)
	splitxml = cont._xml[0]
	try:
		newxml = next(parentxml.iterchildren(AR_NS+cont._binding._xmlname))
	except:
		_element, key = cont._parent._xmlchildren[cont._binding._xmlname]
		newxml = parentxml.makeelement(AR_NS+cont._binding._xmlname)
		for child in parentxml:
			if cont._parent._xmlchildren[no_ns(child.tag)][1] > key:
				child.addprevious(newxml)
				break
		else:
			parentxml.append(newxml)
	if newxml.tag != splitxml.tag:
		# skip sort
		newxml = ET.SubElement(newxml,splitxml.tag)
	
	newxml.text=splitxml.text
	newxml.attrib.update(splitxml.attrib)
	for key in cont._binding._splitkey:
		splitcontainer = splitxml.find(key, AR)
		if splitcontainer is not None:
			xpath = []
			while splitcontainer is not splitxml:
				xpath.append(splitcontainer)
				splitcontainer = splitcontainer.getparent()
			parent = newxml
			for x in reversed(xpath):
				try:
					nextx = next(parent.iterchildren(x.tag))
				except:
					# sorted if splitkey sorted
					nextx = ET.SubElement(parent,x.tag, x.attrib)
					nextx.text = x.text
				parent = nextx
	e=type(cont)()
	e._setxml(newxml,cont._binding,cont._parent)
	cont._appendxml(e)
	return newxml
	
@classmethod
def _create_ComplexTypeBase(newtype, parent,element,file,val=None,setup=True):
	newxml=_createxml(parent,element,file,newtype,setup=setup)
	if issubclass(newtype, Group.Referrable):
		namedParent=parent
		if not isinstance(namedParent, (autosar_r4p0.Group.Referrable, autosar_r4p0.Group.AUTOSAR)):
			namedParent=namedParent._parent
		if val is not None:
			assert not getattr(namedParent, val, None), f'Cannot create container with name {val}, name already exist in parent {parent}'
		else:
			idx = 0
			while True:
				val = f'{newtype.__name__}_{idx}'
				if not hasattr(namedParent, val):
					break
				idx += 1
		binding = getattr(newtype, 'shortName')
		val = binding._desttype._valueType(val)
		shortName = ET.SubElement(newxml, AR_NS + 'SHORT-NAME')
		shortName.text = str(val)
	elif val is not None:
		val=newtype._valueType(val)
		val=val.__str__() if not isinstance(val,simplebase.Enum) else val.value
		newxml.text=val
	e=newtype()
	e._setxml(newxml,element,parent)
	if setup:
		e._setup()
	if isinstance(element, Elements):
		childref=parent.__dict__[element._childname]
		if element._isOrdered:
			childref.append(e)
		else:
			bisect.insort(childref,e,key=element._sortkey)
	else:
		parent.__dict__[element._childname]=e
	return e

@classmethod
def _create_ContainerBase(newtype, parent,element,file,val=None,setup=True):
	newxml=_createxml(parent,element,file,newtype,setup=setup)
	assert val is not None
	if issubclass(newtype, ModelReference):
		if type(val) is Editor:
			val=val._instance
		if isinstance(val,Group.Referrable):
			value='/'+'/'.join(val.__repr__().split('.')[1:])
		else:
			value=val
			try:
				assert value[0]=='/','Currently only support for setting reference by string if reference is absolute and loaded'
				val=functools.reduce(getattr,value.split('/')[1:],autosar)
			except:
				#raise AssertionError('Referrences must either be set with a valid referece or a string with the referece to a loaded object')
				val=None
				# cannot set DEST if referenced model not loaded
		newxml.text=value
		if val!=None:
			xmlname=None
			for subtype in val.__class__.__mro__[1:]:
				try:
					xmlname=next((xmlname for xmlname,group in newtype._dests.items() if group is subtype))
					break
				except:
					pass
			assert xmlname is not None, f'{val} is not a valid destination for property'
			newxml.attrib['DEST']=xmlname
	else:
		val=newtype._valueType(val)
		val=val.__str__() if not isinstance(val,simplebase.Enum) else val.value
		newxml.text=val
	e=newtype()
	if setup and element._name=='shortName':
		parent._unsetup()
		parent.__dict__[element._childname]=e
		e._setxml(newxml,element,parent)
		parent._setup()
	else:
		e._setxml(newxml,element,parent)
		if setup:
			e._setup()
		if isinstance(element, Elements):
			childref=parent.__dict__[element._childname]
			if element._isOrdered:
				childref.append(e)
			else:
				bisect.insort(childref,e,key=element._sortkey)
		else:
			parent.__dict__[element._childname]=e
	return e

ContainerBase._create = _create_ContainerBase
ComplexTypeBase._create = _create_ComplexTypeBase

def _createchild_ContainerBase(self,name,selectedFile,val=None):
	# multiplicity is assumed to be checked before this call
	binding=getattr(type(self),name,None)
	assert binding is not None,f'No child named {name} in {self.__repr__()}'
	if type(binding) is Attribute:
		if val is not None:
			assert selectedFile is None or len(self._xml)==1 and selectedFile==self._xml[0].base,'File selection makes it impossible to update attribute'
			val=str(binding._valueType(val))
			for xml in self._xml:
				fileH.modified(xml.base)
				xml.attrib[binding._xmlname]=val
		return ModelNone
	assert type(binding) in (Element,Elements), f'{name} is not a valid property of container'
	if isinstance(binding._desttype,dict):
		if val is None:
			newtype=next(binding._desttype.values().__iter__())
			assert len(binding._desttype)==1, 'Cannot create arraymember when multiple member types supported and no type selected'
		else:
			newtype=val.__class__
			assert newtype in binding._desttype, 'value type set not allowed'
	else:
		newtype=binding._desttype
		if binding._property:
			if val is None and not newtype._atpMixed:
				return ModelNone
	files=iter((xml.base for xml in self._xml) if selectedFile is None else (selectedFile,))
	assert binding._splitable or selectedFile or len(self._xml) == 1, f'Cannot create child {name} to splitted parent {self} without specifying target file'		
	newcont=newtype._create(self,binding,next(files),val)
	for file in files:
		_complement(newcont,file)
	return newcont
ContainerBase._createchild=_createchild_ContainerBase

def _move_ContainerBase(self,to_parent,to_idx,file):
	raise AssertionError('Currently no support')
ContainerBase._move=_move_ContainerBase

def _move_EcucIndexableValue(self,to_parent,to_idx,file):
	if not self.definition.ref().requiresIndex.val():
		return super(self,Group.EcucIndexableValue)._move(self,to_parent,to_idx,file)
	assert self.parent()==to_parent, 'No support yet to move containers between parents'
	assert file is None or len(self._xml)==1 and self._xml[0].base==file,'No support yet for file split move'
	newlist=list(self.ecucBinding())
	newlist.remove(self)
	newlist.insert(to_idx, self)
	if file:
		for idx,e in enumerate(newlist):
			if e.index.val()!=idx:
				assert len(e._xml)==1 and self._xml[0].base==file,f'Cannot update index of container {e} since it is not completely stored in file {file}'
	for idx,e in enumerate(newlist):
		if e.index.val()!=idx:
			e.editor(file).index=idx
Group.EcucIndexableValue._move=_move_EcucIndexableValue


def _set_ModelReference(self,selectedFile,value):
	if isinstance(value, str):
		assert value[0]=='/', 'currently only support for setting absolute paths, no relative ones'
		try:
			obj = eval('autosar'+value.replace('/','.'))
			if obj:
				# use the object instead of the reference to make sure that the DEST attribute is updated
				value = obj
		except:
			pass
	if isinstance(value,autosar_r4p0.Group.Referrable):
		if type(value) is Editor:
			value=value._instance
		if self.val()==str(value) and self.DEST.value in [xmlname for xmlname,group in self._dests.items() if isinstance(value,group)]:
			# both value and DEST is correct, ignore and return
			return
		self._unsetup()
		xmlname=None
		for subtype in value.__class__.__mro__:
			if subtype in type(self)._dests.values():
				xmlname,group=next(((xmlname,group) for xmlname,group in type(self)._dests.items() if group is subtype))
				break
		assert xmlname is not None, f'{value} is not a valid destination for property'
		value='/'+'/'.join(value.__repr__().split('.')[1:])
		for xml in self._xml:
			fileH.modified(xml.base)
			xml.text=value
			xml.attrib['DEST']=xmlname
		self._setup()
		return
	super(ModelReference,self)._set(selectedFile,value)
ModelReference._set=_set_ModelReference
	
def _set_ValueTypeBase(self,selectedFile,value):
	toval=self._valueType(value)
	if toval==self.val():
		return
	if self._binding._name=='shortName':
		namedParent=self._parent._parent
		if not isinstance(namedParent, (autosar_r4p0.Group.Referrable, autosar_r4p0.Group.AUTOSAR)):
			namedParent=namedParent._parent
		if getattr(namedParent, value, ModelNone):
			raise ValueError(f'Trying to set a name {value} already taken in the namespace {namedParent} which is not allowed')
		self._parent._unsetup()
		if selectedFile is not None:
			assert selectedFile in (xml.base for xml in self._xml), 'Automat error'
			if len(self._xml)>1:
				raise AssertionError('Currently no support for model copy by rename')
# 					editor=Edit(self._parent,selectedFile)
# 					xml=next(xml for xml in self._parent._xml if xml.base==str(selectedFile))
# 					newxml=deepcopy(xml)
# 					newxml.find(AR_NS+'SHORT-NAME').text=str(value)
# 					xml.getparent().append(newxml)
# 					editor.delete()
# 					newchild=type(self._parent)()
# 					newchild._setxml(newxml,self._parent._binding,self._parent._parent)
# 					self._parent._merge(newchild,self._parent._binding._splitkey)
# 					return
		for xml in self._xml:
			fileH.modified(xml.base)
			xml.text=value
		self._parent._setup()
		return
	if selectedFile is not None:
		assert len(self._xml)==1, f'Cannot set splitted property {self} when not all variants are updated, files: {list(e.base for e in self._xml)}'
		assert selectedFile==self._xml[0].base, f'Cannot split property {self}, selectedFile:{selectedFile}, existingfile: {self._xml[0].base}'
	self._unsetup()
	for xml in self._xml:
		fileH.modified(xml.base)
		toval=toval.__str__() if not isinstance(toval,simplebase.Enum) else toval.value
		xml.text=toval
	self._setup()
	#self._setup()
ValueTypeBase._set=_set_ValueTypeBase

def _set_ComplexTypeBase(self,selectedFile,value):
	raise AssertionError('Currently no support for setting non properties')
ComplexTypeBase._set=_set_ComplexTypeBase

def _set_EcucTextualParamValue(self,selectedFile,value):
	#todo validate value
	Editor(self).value=value
Group.EcucTextualParamValue._set=_set_EcucTextualParamValue
def _set_EcucNumericalParamValue(self,selectedFile,value):  
	#todo validate value
	if isinstance(value,str) and isinstance(self.definition.ref(),autosar_r4p0.EcucBooleanParamDef):
		if value=='true':
			value=1
		elif value=='false':
			value=0
		else:
			raise ValueError(f'{value} is not a valid value for {self.definition.ref().shortName.val()}')
	Editor(self).value=value
Group.EcucNumericalParamValue._set=_set_EcucNumericalParamValue
def _set_EcucAddInfoParamValue(self,selectedFile,value):
	#todo validate value
	Editor(self).value=value
Group.EcucAddInfoParamValue._set=_set_EcucAddInfoParamValue
def _set_EcucReferenceValue(self,selectedFile,value):
	#todo validate value
	Editor(self).value=value
Group.EcucReferenceValue._set=_set_EcucReferenceValue
def _set_EcucInstanceReferenceValue(self,selectedFile,value):
	value = eval(str(value))
	editor = Editor(self.value)
	editor.target = value.model()
	editor.contextElement.delete()
	editor.contextElement.append(value.context())
Group.EcucInstanceReferenceValue._set=_set_EcucInstanceReferenceValue
# def _createchild_EcucNumericalParamValue(self,name,selectedFile,val=None):
# 	#todo: validate value
# 	super(Group.EcucNumericalParamValue,self)._createchild(name,selectedFile,val)
# Group.EcucNumericalParamValue._createchild=_createchild_EcucNumericalParamValue

def _createchild_EcucModuleConfigurationValues(self,name,selectedFile,val=None):
	# multiplicity is assumed to be checked before this call
	try:
		_def=self.definition.ref()
		newdef=getattr(_def,name)
		if not isinstance(newdef,Group.EcucDefinitionElement):
			raise
	except:
		return super(Group.EcucModuleConfigurationValues,self)._createchild(name,selectedFile,val)
	if val is None:
		if newdef.upperMultiplicity.val() == 1:
			val = name
		else:
			idx = 0
			while hasattr(self, f'{name}_{idx}'):
				idx += 1
			val = f'{name}_{idx}'
		existing = None
	else:
		existing = getattr(self, val, None)
	if existing:
		assert isinstance(existing, autosar_r4p0.EcucContainerValue) and not existing.definition, f'{self} already contain a container of name {val}'
		ret = existing
	else:
		ret = self.container._createchild(selectedFile, val)
	Editor(ret, selectedFile).definition=newdef
	return ret
Group.EcucModuleConfigurationValues._createchild=_createchild_EcucModuleConfigurationValues

def _createchild_EcucContainerValue(self,name,selectedFile,val=None):
	# multiplicity is assumed to be checked before this call
	try:
		_def=self.definition.ref()
		newdef=getattr(_def,name)
		if not isinstance(newdef,Group.EcucDefinitionElement):
			raise
	except:
		return super(Group.EcucContainerValue,self)._createchild(name,selectedFile,val)
	if isinstance(newdef,Group.EcucContainerDef):
		if val is None:
			if newdef.upperMultiplicity.val() == 1:
				val = name
			else:
				idx = 0
				while hasattr(self, f'{name}_{idx}'):
					idx += 1
				val = f'{name}_{idx}'
			existing = None
		else:
			existing = getattr(self, val, None)
		if existing:
			assert isinstance(existing, autosar_r4p0.EcucContainerValue) and not existing.definition, f'{self} already contain a subcontainer of name {val}'
			ret = Editor(existing, selectedFile)
		else:
			ret = self.editor(file=selectedFile).subContainer.append(val)
		ret.definition=newdef
		return ret._instance
	elif isinstance(newdef,Group.EcucParameterDef):
		if val is None:
			#ended up heare because trying to read a property not set, return ModelNone
			return ModelNone
		#assert val is not None,'Property must have value specified'
		if isinstance(newdef,Group.EcucIntegerParamDef):
			ret=self.parameterValue.EcucNumericalParamValue
		elif isinstance(newdef,Group.EcucEnumerationParamDef):
			ret=self.parameterValue.EcucTextualParamValue
		elif isinstance(newdef,Group.EcucFloatParamDef):
			ret=self.parameterValue.EcucNumericalParamValue
		elif isinstance(newdef,Group.EcucFunctionNameDef):
			ret=self.parameterValue.EcucTextualParamValue
		elif isinstance(newdef,Group.EcucLinkerSymbolDef):
			ret=self.parameterValue.EcucTextualParamValue
		elif isinstance(newdef,Group.EcucMultilineStringParamDef):
			ret=self.parameterValue.EcucTextualParamValue
		elif isinstance(newdef,Group.EcucStringParamDef):
			ret=self.parameterValue.EcucTextualParamValue
		elif isinstance(newdef,Group.EcucAddInfoParamDef):
			ret=self.parameterValue.EcucAddInfoParamValue
		elif isinstance(newdef,Group.EcucBooleanParamDef):
			if val==True or val=='true':
				val=1
			elif val==False or val=='false':
				val=0
			else:
				raise ValueError(f'Cannot set value {value} to boolean parameter')
			ret=self.parameterValue.EcucNumericalParamValue
		else:
			raise AssertionError('automat error')
		# add one container
		ret=ret._createchild(selectedFile)
		ret._createchild('definition', selectedFile,newdef)
		#ret.definition=newdef
		ret._createchild('value',selectedFile,val)
		return ret
	elif isinstance(newdef,Group.EcucAbstractReferenceDef):
		if val is None:
			#ended up heare because trying to read a property not set, return ModelNone
			return ModelNone
		#assert val is not None,'Property must have value specified'
		ret=self.editor(file=selectedFile)
		if isinstance(newdef,Group.EcucInstanceReferenceDef):
			val = eval(str(val))
			assert isinstance(val, InstanceRef), "Trying to set an instance ref with something that is not an instance"
			ret=ret.referenceValue.EcucInstanceReferenceValue.append()
			ret.value.contextElement.append(val.context())
			ret.value.target = val.model()
		else:
			ret=ret.referenceValue.EcucReferenceValue.append()
			ret.value=val
		ret.definition=newdef
		return ret._instance
	else:
		raise AssertionError('Automat error')
Group.EcucContainerValue._createchild=_createchild_EcucContainerValue
#todo: add value validator class
#autosar_r4p0.NumericalValueVariationPoint._valueType=

def _createchild_ElementList(self,selectedFile,val=None):
	# multiplicity is assumed to be checked before this call
	#assert self._element._numInstancesMax > self.__len__(), f"Error: trying to create {self.binding().__str__()} element when max elements {self._element._numInstancesMax} already exists"
	if isinstance(self._element._desttype,dict):
		#assert val is None,'Currently only support for setting values to properties, no array updates'
		newtype=self.__dict__.get('_selected')
		if newtype==None:
			if val is not None:
				# special handling: if value specified then only look at subtypes where val is supported
				desttypes = list(t for t in self._element._desttype.values() if issubclass(t, (ValueTypeBase,autosar_r4p0.Group.Referrable)))
			else:
				desttypes = list(t for t in self._element._desttype.values() if not issubclass(t, ValueTypeBase))
			assert len(desttypes)==1, 'Cannot create array member when multiple member types supported and no type selected'
			newtype=next(desttypes.__iter__())
	else:
		newtype=self._element._desttype
	if issubclass(newtype, ComplexTypeBase):
		assert val is None or issubclass(newtype,Group.Referrable) or self.binding().isProperty(),'Currently no support for setting non property array members'
	else:
		assert val is not None,'Cannot create property array member without value specified'
	files=iter((xml.base for xml in self._instance._xml) if selectedFile is None else (selectedFile,))
	newcont=newtype._create(self._instance,self._element,next(files),val)
	for file in files:
		_complement(newcont,file)
	return newcont
ElementList._createchild=_createchild_ElementList

def _createchild_EcucList(self,selectedFile,val=None):
	# multiplicity is assumed to be checked before this call
	_def=self._definition
	#assert len(self) < (_def.upperMultiplicity.val() or math.inf),'upperMultiplicity already reached'
	if isinstance(_def,Group.EcucContainerDef): #EcucParamConfContainerDef
		if val is None:
			val=_def.shortName.val()
			if _def.upperMultiplicity.val() != 1:
				idx = 0
				while hasattr(self._instance, f'{val}_{idx}'):
					idx += 1
				val = f'{val}_{idx}'
			existing = None
		else:
			existing = getattr(self._instance, val, None)
		if existing:
			assert isinstance(existing, autosar_r4p0.EcucContainerValue) and not existing.definition, f'{self} already contain a subcontainer of name {val}'
			ret = Editor(existing, selectedFile)
		else:
			if isinstance(self._instance,Group.EcucModuleConfigurationValues):
				ret = Editor(self._instance.container, selectedFile).append(val)
			else:
				ret = Editor(self._instance.subContainer, selectedFile).append(val)
		ret.definition=_def
		ret=ret._instance
	elif isinstance(_def,Group.EcucAbstractReferenceDef): #EcucReferenceDef
		assert val, f'Cannot create a child of type {_def} without a specified value'
		assert selectedFile or len(self._instance._xml) == 1, f'Cannot create child of type {_def} to splitted parent {self._instance} without specifying target file'
		ret=self._instance._createchild(_def.shortName.val(),selectedFile,val)
	else:
		assert val, f'Cannot create a child of type {_def} without a specified value'
		assert selectedFile or len(self._instance._xml) == 1, f'Cannot create child of type {_def} to splitted parent {self._instance} without specifying target file'
		ret=self._instance._createchild(_def.shortName.val(),selectedFile,val)
	if _def.requiresIndex.val():
		newidx = 0
		for child in self:
			if child is ret:
				# this should be last, wait with it
				continue
			if not child.index:
				# missing index, set it
				child._createchild('index', selectedFile, newidx)
				newidx+=1
			else:
				newidx=child.index.val()+1
		ret._createchild('index', selectedFile, newidx)
	return ret
EcucList._createchild=_createchild_EcucList

class Editor(object):
	def _complement(self):
		if isinstance(self._instance, Aggregation):
			if self._instance:
				# if someone else has created it after the editor was created
				object.__setattr__(self,'_instance',self._instance[0])
			else:
				object.__setattr__(self,'_instance',self._instance._instance._createchild(self._instance.name(),self._file))
	@property
	def __class__(self):
		if isinstance(self._instance, Aggregation):
			if isinstance(self._instance, EcucAggregation):
				return autosar_r4p0.EcucContainerValue
			return self._instance.desttype()
		return type(self._instance)
	@property
	def __dict__(self):
		self._complement()
		return self._instance.__dict__
	def __repr__(self):
		self._complement()
		args=[]
		if self._file is not None:
			args.append("file="+self._file._file)
		return f'{self._instance.__repr__()}.editor({",".join(args)})'
	def __eq__(self, other):
		return self._instance==other
	def __ne__(self, other):
		return self._instance!=other
	def move(self,to_parent,to_idx=None):
		''' moves this container to new parent and new index. Container will be kept, or if file selected, only that file content will be moved'''
		self._complement()
		self._instance._move(to_parent, to_idx, self._file)
	def model(self):
		''' Returns the model referenced by editor'''
		self._complement()
		return self._instance
	def _append(self, val):
		self._complement()
		num_max = self._instance.binding().upperMultiplicity()
		assert num_max == 4294967295 or len(self._instance) < num_max, f"Upper multiplicity for {self._instance} reached"
		return Editor(self._instance._createchild(self._file,val),self._file)
	def __init__(self, instance,file=None):
		object.__setattr__(self,'_instance',instance)
		object.__setattr__(self,'_file',file)
		if isinstance(instance, ElementList):
			object.__setattr__(self,'append',lambda val=None: self._append(val))
	def __dir__(self):
		self._complement()
		ret=list((d for d in type.__dir__(Editor) if d[0]!='_'))
		if hasattr(self,'append'):
			ret.append('append')
		ret+=list((d for d in object.__dir__(self._instance) if d[0]!='_'))
		ret+=self._instance.__dir__()
		if isinstance(self._instance,(Group.EcucModuleConfigurationValues,Group.EcucContainerValue)) and self._instance.definition.ref()!=None:
			ret+=self._instance.definition.ref().children().shortName.val()
		return list(set(ret))
	def set(self,value):
		self._complement()
		assert not isinstance(self._instance,ModelList),'Cannot set value to array of elements'
		self._instance._set(self._file,value)
		return
	def __iter__(self):
		self._complement()
		def generator():
			for i in self._instance.__iter__():
				yield Editor(i,self._file)
		return generator()
	def __bool__(self):
		return bool(self._instance)
	def __getattr__(self,name):
		self._complement()
		ret=getattr(self._instance,name)
		if type(ret) is ElementList:
			if ret._element._numInstancesMax==1:
				#if len(ret)==1:
				#	return Editor(ret[0],self._file)
				if isinstance(ret._element._desttype,type) or ret._selected is not None:
					return Editor(ret,self._file)[0]
			return Editor(ret,self._file)
		elif isinstance(ret,ModelList):
			return Editor(ret,self._file)
		elif isinstance(ret,ContainerBase):	
			return Editor(ret,self._file)
		elif ret is ModelNone:
			assert isinstance(self._instance,ContainerBase),'Automat error'
			if isinstance(self._instance, (Group.EcucContainerValue, Group.EcucModuleConfigurationValues)):
				aggrs = self._instance.ecucAggregations()
				if hasattr(aggrs, name):
					aggr = getattr(aggrs, name)
					if aggr.isProperty():
						return ModelNone
					return Editor(aggr,self._file)
			aggrs = self._instance.aggregations()
			aggr = getattr(aggrs, name)
			if not issubclass(aggr.desttype(),ComplexTypeBase):
				return ModelNone
			return Editor(aggr,self._file)
		elif callable(ret):
			def op(*args,**kwargs):
				res=ret(*args,**kwargs)
				if issubclass(type(res),ContainerBase): # use subclass instead of isinstance to handle edit call
					return Editor(res,self._file)
				return res
			return op
		else:
			return ret
	def __setattr__(self, name, value):
		self._complement()
		if value == None:
			# setting a model element to None: delete it
			delattr(self, name)
			return
		if not isinstance(self._instance,ContainerBase):
			assert self._instance._element._numInstancesMax==1,'Cannot set parameters on arrays'
			if len(self._instance)==0:
				setattr(self[0],name,value)
				return
			object.__setattr__(self,'_instance',self._instance.__iter__().__next__())
		binding=getattr(type(self._instance),name,None)
		if isinstance(binding,Attribute):
			valuetypevalue=binding._valueType(value)
			value=valuetypevalue.__str__() if not isinstance(valuetypevalue,simplebase.Enum) else valuetypevalue.value
			assert self._file is None or len(self._xml)==1 and self._file==self._xml[0].base,'Cannot update attribute when file specified on splitted model'			
			for xml in self._xml:
				xml.attrib[binding._xmlname]=value
				fileH.modified(xml.base)
			return
		res=getattr(self._instance,name)
		assert not isinstance(res,ModelList),'Cannot set parameters on arrays'
		if not res or isinstance(res,DefaultValueType):
			self._instance._createchild(name,self._file, value)
		else:
			res._set(self._file,value)
	def __getitem__(self,key):
		#if not isinstance(self._instance,ModelList):
		#	raise AttributeError('Object is not a list')
		self._complement()
		if isinstance(key,int):
			length=len(self._instance)
			if key >=0:
				if key>=length:
					for i in range(length,key):
						self.append()
					return self.append()
			else:
				key=1-key
				if key>=length:
					raise IndexError("currently no support for insert before")
			ret=self._instance[key]
			return Editor(ret,self._file)
		elif isinstance(key,str):
			try:
				ret=self._instance[key]
				if ret!=ModelNone:
					return Editor(ret,self._file)
			except (KeyError, IndexError) as e:
				# no such key/index, create it
				pass
			assert isinstance(self._instance,ElementList),'Cannot create named container on this list.'
			#assert issubclass(self._instance._element._desttype.values().__iter__().__next__(),Group.Referrable), 'This element does not contain named containers'
			return self.append(key)
		else:
			raise AssertionError('Index key type not supported')
	def __setitem__(self,key,value):
		self._complement()
		if not isinstance(self._instance,ModelList):
			raise AttributeError('Object is not a list')
		if isinstance(key,int):
			length=len(self._instance)
			if key>=0:
				if key>=length:
					for i in range(length,key):
						self.append()
					return self.append(value)
			else:
				key=-1-key
				if key>=length:
					assert 0,'insert before currently not supported'
			res=self._instance[key]
			return Editor(res,self._file).set(value)
		else:
			raise AttributeError('Cannot index arrays with names when setting properties')
	def delete(self):
		if isinstance(self._instance, ModelList):
			for i in self._instance:
				self._delete(i)
		elif isinstance(self._instance, Aggregation):
			# nothing exists, just return
			return
		else:
			self._delete(self._instance)
	def _delete(self,cont):
		def _del(cont):
			cont._unsetup()
			if cont._parent:
				if isinstance(cont,ComplexTypeBase):
					for element,_ in cont._xmlchildren.values():
						parent=cont
						while not isinstance(parent,element._parenttype):
							parent=parent._parent
						c=parent.__dict__[element._childname]
						if isinstance(c,list):
							c.clear()
						elif c is not ModelNone:
							parent.__dict__[element._childname]=ModelNone
				element=cont._binding
				parent=cont._parent
				c=parent.__dict__[element._childname]
				if isinstance(c,list):
					c.remove(cont)
				else:
					parent.__dict__[element._childname]=ModelNone
		def _updatemodel(cont):
			xmls=tuple(xml for xml in cont._xml if xml.base!=self._file)
			if len(xmls)==0:
				_del(cont)
			if len(xmls)==len(cont._xml):
				return
			cont._xml=xmls
			if isinstance(cont,ComplexTypeBase):
				for element,_ in cont._xmlchildren.values():
					parent=cont
					while not isinstance(parent,element._parenttype):
						parent=parent._parent
					c=parent.__dict__[element._childname]
					if isinstance(c,list):
						for child in c[:]:
							_updatemodel(child)
					elif c is not ModelNone:
						_updatemodel(c)

		parent=cont._parent
		if self._file is None:
			_del(cont)
			xmls=cont._xml
		else:
			xml=next((xml for xml in cont._xml if xml.base==self._file),None)
			if xml is None:
				return
			_updatemodel(cont)
		if not parent:
			if self._file:
				self._file.delete()
			else:
				fileH.deleteallfiles()
		else:
			if self._file is None:
				for xml in xmls:
					parentxml = xml.getparent()
					parentxml.remove(xml)
					fileH.modified(xml.base)
					if not parentxml.getchildren() and isinstance(cont._binding,Elements) and isinstance(cont._binding._desttype,dict):
						# grouped values with empty group, delete group
						parentxml.getparent().remove(parentxml)
			else:
				parentxml = xml.getparent()
				parentxml.remove(xml)
				fileH.modified(self._file)
				if not parentxml.getchildren() and isinstance(cont._binding,Elements) and isinstance(cont._binding._desttype,dict):
					# grouped values with empty group, delete group
					parentxml.getparent().remove(parentxml)

	def __delattr__(self, name):
		self._complement()
		e=getattr(self._instance,name)
		if e:
			if isinstance(e, ModelList):
				for i in e:
					self._delete(i)
			else:
				self._delete(e)
	def save(self):
		self._complement()
		def savefile(file):
			if not file._readonly and fileH.clearmodified(file) and str(file) != '':
				f = open(str(file),'wt',encoding='utf-8')
				f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
				autosar_r4p0.AUTOSAR._sortxml(file._root.getroot())
				ET.indent(file._root, space="  ")
				content = ET.tostring(file._root, method="c14n")
				if b'xml:base="' in content:
					# remove xml:base="...."
					firstsplit = content.split(b'xml:base="', 1)
					secondsplit = firstsplit[1].split(b'"', 1)
					content = firstsplit[0] + secondsplit[1]
				f.write(content.decode("UTF8"))  # ,pretty_print=True
				f.close()
					#file._root.write(str(file),xml_declaration=False, doctype='<?xml version="1.0" encoding="utf-8"?>',method="c14n",pretty_print=True)
		if self._file:
			savefile(self._file)
		else:
			filenames=set((xml.base for cont in self._instance for xml in cont._xml))
			for filename in filenames:
				file=fileH.get(filename)
				savefile(file)
			if self._instance is autosar:
				fileH.deleteondisk()
		
	def readonly(self):
		self._complement()
		filenames=set((xml.base for cont in self._instance for xml in cont._xml))
		for filename in filenames:
			filename=filename.removeprefix('file:/')
			if fileH.get(filename).readonly():
				return True
		return False
		
